#import <CoreFoundation/CoreFoundation.h>
#import <CoreServices/CoreServices.h>
#import <QuickLook/QuickLook.h>
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>

#include "arch_info.h"

const int maxLength=1024*32;
const bool USE_RFT=false;

NSString *print_arch(
                     const struct fat_arch *fat_arch)
{
	switch(fat_arch->cputype){
        case CPU_TYPE_MC680x0:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_MC680x0_ALL:
                    return (@" m68k");
                    break;
                case CPU_SUBTYPE_MC68030_ONLY:
                    return (@" m68030");
                    break;
                case CPU_SUBTYPE_MC68040:
                    return (@" m68040");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_POWERPC:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_POWERPC_ALL:
                    return (@" ppc");
                    break;
                case CPU_SUBTYPE_POWERPC_601:
                    return (@" ppc601");
                    break;
                case CPU_SUBTYPE_POWERPC_603:
                    return (@" ppc603");
                    break;
                case CPU_SUBTYPE_POWERPC_603e:
                    return (@" ppc603e");
                    break;
                case CPU_SUBTYPE_POWERPC_603ev:
                    return (@" ppc603ev");
                    break;
                case CPU_SUBTYPE_POWERPC_604:
                    return (@" ppc604");
                    break;
                case CPU_SUBTYPE_POWERPC_604e:
                    return (@" ppc604e");
                    break;
                case CPU_SUBTYPE_POWERPC_750:
                    return (@" ppc750");
                    break;
                case CPU_SUBTYPE_POWERPC_7400:
                    return (@" ppc7400");
                    break;
                case CPU_SUBTYPE_POWERPC_7450:
                    return (@" ppc7450");
                    break;
                case CPU_SUBTYPE_POWERPC_970:
                    return (@" ppc970");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_POWERPC64:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_POWERPC_ALL:
                    return (@" ppc64");
                    break;
                case CPU_SUBTYPE_POWERPC_970:
                    return (@" ppc970-64");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_VEO:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_VEO_1:
                    return (@" veo1");
                    break;
                case CPU_SUBTYPE_VEO_2:
                    return (@" veo2");
                    break;
                case CPU_SUBTYPE_VEO_3:
                    return (@" veo3");
                    break;
                case CPU_SUBTYPE_VEO_4:
                    return (@" veo4");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_MC88000:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_MC88000_ALL:
                case CPU_SUBTYPE_MC88110:
                    return (@" m88k");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_I386:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_I386_ALL:
                    /* case CPU_SUBTYPE_386: same as above */
                    return (@" i386");
                    break;
                case CPU_SUBTYPE_486:
                    return (@" i486");
                    break;
                case CPU_SUBTYPE_486SX:
                    return (@" i486SX");
                    break;
                case CPU_SUBTYPE_PENT: /* same as 586 */
                    return (@" pentium");
                    break;
                case CPU_SUBTYPE_PENTPRO:
                    return (@" pentpro");
                    break;
                case CPU_SUBTYPE_PENTII_M3:
                    return (@" pentIIm3");
                    break;
                case CPU_SUBTYPE_PENTII_M5:
                    return (@" pentIIm5");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_X86_64:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_X86_64_ALL:
                    return (@" x86_64");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_I860:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_I860_ALL:
                case CPU_SUBTYPE_I860_860:
                    return (@" i860");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_HPPA:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_HPPA_ALL:
                case CPU_SUBTYPE_HPPA_7100LC:
                    return (@" hppa");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_SPARC:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_SPARC_ALL:
                    return (@" sparc");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_ARM:
            switch(fat_arch->cpusubtype){
                case CPU_SUBTYPE_ARM_ALL:
                    return (@" arm");
                    break;
                case CPU_SUBTYPE_ARM_V4T:
                    return (@" armv4t");
                    break;
                case CPU_SUBTYPE_ARM_V5TEJ:
                    return (@" armv5");
                    break;
                case CPU_SUBTYPE_ARM_XSCALE:
                    return (@" xscale");
                    break;
                case CPU_SUBTYPE_ARM_V6:
                    return (@" armv6");
                    break;
                case CPU_SUBTYPE_ARM_V7:
                    return (@" armv7");
                    break;
                case CPU_SUBTYPE_ARM_V7F:
                    return (@" armv7f");
                    break;
                case CPU_SUBTYPE_ARM_V7S:
                    return (@" armv7s");
                    break;
                case CPU_SUBTYPE_ARM_V7K:
                    return (@" armv7k");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        case CPU_TYPE_ANY:
            switch(fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK){
                case CPU_SUBTYPE_MULTIPLE:
                    return (@" any");
                    break;
                case CPU_SUBTYPE_LITTLE_ENDIAN:
                    return (@" little");
                    break;
                case CPU_SUBTYPE_BIG_ENDIAN:
                    return (@" big");
                    break;
                default:
                    goto print_arch_unknown;
            }
            break;
        print_arch_unknown:
        default:
            return [NSString stringWithFormat:@" UNKNOWN cputype (%d) cpusubtype (%d)", fat_arch->cputype,
                    fat_arch->cpusubtype & ~CPU_SUBTYPE_MASK];
            break;
	}
}

NSString *getArchitectureInfo(const struct input_file *input, const struct thin_file *thin) {
    if (input->fat_header != NULL){
        NSMutableString *archString=[NSMutableString stringWithCapacity:16];
        for(int j = 0; j < input->fat_header->nfat_arch; j++){
            [archString appendString: print_arch(&(input->fat_arches[j]))];
        }
        return archString;
    } else {
        NSString *archString=print_arch(&(thin->fat_arch));
        return archString;
    }
}

extern "C" {
    OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options);
    void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview);
}

OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSMutableDictionary *props;
    if (USE_RFT) {
        props = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"text/rtf", kQLPreviewPropertyMIMETypeKey, nil];
    } else {
        props = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"text/plain", kQLPreviewPropertyMIMETypeKey, nil];
    }
    
    CFStringRef pathString=CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
    CFIndex pathLength=CFStringGetMaximumSizeOfFileSystemRepresentation(pathString);
    char *path=(char *)malloc(pathLength);
    CFStringGetFileSystemRepresentation(pathString, path, pathLength);
    
    FILE *f=fopen(path, "r");
    if (!f) {
        [pool release];
        return noErr;
    }
    
    UInt8 *textBuffor=(UInt8 *)malloc(maxLength+1);
    
    size_t readLength=fread(textBuffor, 1, maxLength, f);
    if (readLength<=0) {
        free(textBuffor);
        [pool release];
        return noErr;
    }
    
    textBuffor[readLength]=0;
    
    
    int fd=fileno(f);
    struct stat fileStat;
    NSMutableString *infoString=nil;
    
    int ok=fstat(fd, &fileStat);
    if (ok==0){
        
        char sizeString[8];
        
        if (fileStat.st_size>=1024*1024*1024) {
            snprintf(sizeString, 8, "%.1f GB", (float)fileStat.st_size/(1024*1024*1024));
        } else if (fileStat.st_size>=1024*1024){
            snprintf(sizeString, 8, "%.1f MB", (float)fileStat.st_size/(1024*1024));
        } else if (fileStat.st_size>=1024){
            snprintf(sizeString, 8, "%d KB", (int)(fileStat.st_size>>10));
        } else {
            snprintf(sizeString, 8, "%d B", (int)(fileStat.st_size));
        }
        
        struct passwd* userEntry=getpwuid(fileStat.st_uid);
        NSString *userName=(userEntry)?[NSString stringWithCString:userEntry->pw_name encoding:NSASCIIStringEncoding]:[NSString stringWithFormat:@"%d", fileStat.st_uid];
        struct group* groupEntry=getgrgid(fileStat.st_gid);
        NSString *groupName=(userEntry)?[NSString stringWithCString:groupEntry->gr_name encoding:NSASCIIStringEncoding]:[NSString stringWithFormat:@"%d", fileStat.st_gid];
        
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
        [dateFormatter setTimeStyle:NSDateFormatterShortStyle];
        
        
        NSString *kind = nil;
        NSString *extension=[((NSURL *) url) pathExtension];
        LSCopyKindStringForURL((CFURLRef)url, (CFStringRef *)&kind);
        if (kind) [kind autorelease];
        else kind=@"";
        
        infoString=[NSMutableString stringWithFormat:@"%@: %s | Modified: %@ | %@[ %s%s%s ] %@[ %s%s%s ] %s%s%s",
                    kind,
                    sizeString,
                    [dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:fileStat.st_mtimespec.tv_sec]],
                    userName, (S_IRUSR & fileStat.st_mode)?"r":"", (S_IWUSR & fileStat.st_mode)?"w":"", (S_IXUSR & fileStat.st_mode)?((S_ISUID & fileStat.st_mode)?"s":"x") : "",
                    groupName, (S_IRGRP & fileStat.st_mode)?"r":"", (S_IWGRP & fileStat.st_mode)?"w":"", (S_IXGRP & fileStat.st_mode)?((S_ISGID & fileStat.st_mode)?"s":"x") : "",
                    (S_IROTH & fileStat.st_mode)?"r":"", (S_IWOTH & fileStat.st_mode)?"w":"", (S_IXOTH & fileStat.st_mode)?((S_ISVTX & fileStat.st_mode)?"t":"x") : ""
                    ];
        
        
        NSString *archString=0;
        if (((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode) || [[NSArray arrayWithObjects:@"a", @"dylib", @"o", nil] containsObject:extension]) {
            struct input_file input;
            struct thin_file thin;
            memset(&input, 0, sizeof(input));
            memset(&thin, 0, sizeof(thin));
            input.name=path;
            input.size=fileStat.st_size;
            
            try {
                process_input_file(fd, &input, &thin);
                archString=getArchitectureInfo(&input, &thin);
                munmap(input.addr, input.size);
            } catch (int i) {
                //                NSLog(@"error checking arch");
            }
        }
        if (archString) {
            [infoString appendFormat:@" |%@", archString];
        }
        
        
        MDItemRef mdItem=MDItemCreateWithURL(NULL, url);
        if (mdItem!=NULL) {
            NSArray *mdArray=[NSArray arrayWithObjects: (NSString *) kMDItemWhereFroms, nil];
            NSDictionary *mdDict=(NSDictionary *)MDItemCopyAttributes(mdItem, (CFArrayRef) mdArray);
            if (mdDict!=nil) {
                for (id key in mdDict) {
                    [infoString appendFormat:@"\n%@:%@", key, [mdDict valueForKey:key]];
                }
                [mdDict release];
            }
            CFRelease(mdItem);
        }
        
        [dateFormatter release];
        
    }
    fclose(f);
    

    NSString *bufforString=[[NSString alloc] initWithBytesNoCopy:textBuffor length:readLength+1 encoding:NSUTF8StringEncoding freeWhenDone:NO];
    if (bufforString==nil) {
        int ii=0;
        for (int i=0; i<readLength; ++i) {
            if (!iscntrl(textBuffor[i])){
                textBuffor[ii++]=textBuffor[i];
            }
        }
        
        bufforString=[[NSString alloc] initWithBytesNoCopy:textBuffor length:ii-1 encoding:NSASCIIStringEncoding freeWhenDone:NO];
    }
    
    
    if (USE_RFT) {
        NSUInteger infoLength=[infoString length]+1;

        NSFont *previewFont=[NSFont userFixedPitchFontOfSize: [NSFont smallSystemFontSize]];
        NSDictionary *rtfDocumment=[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat:0.0f], NSLeftMarginDocumentAttribute, [NSNumber numberWithFloat:0.0f], NSRightMarginDocumentAttribute, [NSNumber numberWithFloat:0.0f], NSTopMarginDocumentAttribute, [NSNumber numberWithFloat:0.0f], NSBottomMarginDocumentAttribute, previewFont, NSFontAttributeName, NSRTFTextDocumentType, NSDocumentTypeDocumentAttribute, nil];

        NSMutableAttributedString *previewString=[[NSMutableAttributedString alloc] initWithString: [NSString stringWithFormat:@"%@\n%@", infoString, bufforString] attributes:rtfDocumment];
        
        [previewString setAttributes:[NSDictionary dictionaryWithObjectsAndKeys: [NSColor whiteColor], NSForegroundColorAttributeName, [NSColor blackColor], NSBackgroundColorAttributeName, previewFont, NSFontAttributeName, nil]
                               range:NSMakeRange(0,infoLength)];
        
        [bufforString release];
        
        QLPreviewRequestSetDataRepresentation(
                                              preview,
                                              (CFDataRef)[previewString RTFFromRange:NSMakeRange(0, [previewString length])
                                                                  documentAttributes:rtfDocumment],
                                              kUTTypeRTF,
                                              (CFDictionaryRef)props);
        [previewString release];

    } else {
        NSString *previewString=[NSString stringWithFormat:@"%@\n%@", infoString, bufforString];
        [bufforString release];
        
        QLPreviewRequestSetDataRepresentation(
                                              preview,
                                              (CFDataRef)[previewString dataUsingEncoding:NSUTF8StringEncoding],
                                              kUTTypePlainText,
                                              (CFDictionaryRef)props);
    }
    
    free(path);
    free(textBuffor);
    [pool release];
    
    return noErr;
}

void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview)
{
}
